Skip to content

feat: Phase 2 — local-LLM fallback (opt-in, human-gated)#7

Merged
constgemm merged 1 commit into
mainfrom
feature/llm-fallback
Jul 7, 2026
Merged

feat: Phase 2 — local-LLM fallback (opt-in, human-gated)#7
constgemm merged 1 commit into
mainfrom
feature/llm-fallback

Conversation

@constgemm

Copy link
Copy Markdown
Owner

Adds llm.py, the Phase 2 fallback that parses the long tail the deterministic templates can't (Ryanair, Wizz, LATAM, OTAs) via an OpenAI-compatible endpoint (Open WebUI on the homelab box).

Safety by construction

  • Runs only on emails the deterministic pass missed, so a normal weekly run makes zero LLM calls unless there's genuinely new long-tail mail.
  • Every LLM candidate is extractor="llm" + confidence="uncertain"classify() routes it to the human-review bucket and _blocked_reason() keeps it out of the unattended --auto-write path. An LLM guess never writes itself.
  • Off unless configured: needs LLM_FALLBACK=1 and LLM_URL/LLM_MODEL (or populate.py --llm).

Changes

  • llm.py — config, OpenAI-compatible /v1/chat/completions client, robust JSON/code-fence parsing, candidate mapping (reuses airdata for ICAO).
  • populate.parse_candidates — per-file fallback when the templates miss.
  • Dockerfile — copies llm.py (populate imports it).
  • .env.example, README — document the opt-in.
  • tests/test_llm.py — 15 tests (parsing, provenance/confidence contract, enable gating, populate integration). Full suite: 44 green.

Needs from you before it can actually call a model: the vmgpu Open WebUI base URL, model name, and an API key (→ .env, never committed).

🤖 Generated with Claude Code

Adds llm.py: an opt-in OpenAI-compatible fallback (Open WebUI) that parses the
emails the deterministic templates miss (Ryanair/Wizz/LATAM/OTAs). It runs only
on files the templates didn't match, and every candidate it returns is
extractor="llm" + confidence="uncertain" by construction — so classify() routes
it to the human-review bucket and _blocked_reason() keeps it out of --auto-write.
An LLM guess never writes itself.

- populate.parse_candidates gains a per-file fallback (env LLM_FALLBACK or --llm).
- Dockerfile copies llm.py (populate imports it).
- 15 tests: JSON/fence parsing, provenance+confidence contract, enable gating,
  and the populate integration (fallback used only when templates miss).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@constgemm
constgemm merged commit 9a2ec27 into main Jul 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant